/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#ifndef CORE_HTTP_HTTPRESPONSE_H_
#define CORE_HTTP_HTTPRESPONSE_H_

#include <string.h>

#include "HttpRequest.h"

namespace aliyun {

namespace http {

class HttpResponse : public HttpRequest {

 public:

  HttpResponse() {
    status_ = 0;
  }

  HttpResponse(std::string url)
      : HttpRequest(url) {
    status_ = 0;
  }

  // override
  void setContent(std::string content, std::string encoding, FormatType format) {
    content_ = content;
    encoding_ = encoding;
    contentType_ = format;
  }

  // override
  std::string getHeaderValue(std::string name) const {
    std::map<std::string, std::string>::const_iterator it;
    it = headers_.find(name);
    if (it != headers_.end())
      return it->second;
    it = headers_.find(utils::StringUtils::ToLowerCase(name));
    if (it != headers_.end())
      return it->second;
    return "";
  }

  // TODO: synchronized(blocking calling thread) fetch a HTTP request.
  static HttpResponse getResponse(HttpRequest request) {
    CURLcode rc;
    HttpResponse response;

    CurlHandle curl = request.getHttpConnection();
    HttpTransaction trans(curl, &request, &response);

    // set up callbacks
    if (request.getMethod() == MethodType::PUT
        || request.getMethod() == MethodType::POST) {
      curl_easy_setopt_throw(curl, CURLOPT_READDATA, &trans);
      curl_easy_setopt_throw(curl, CURLOPT_READFUNCTION, RequestBodyHandler);
      curl_easy_setopt_throw(curl, CURLOPT_INFILESIZE_LARGE, request.getContent().size());
    }

    curl_easy_setopt_throw(curl, CURLOPT_HEADERDATA, &trans);
    curl_easy_setopt_throw(curl, CURLOPT_HEADERFUNCTION, ResponseHeaderHandler);

    curl_easy_setopt_throw(curl, CURLOPT_WRITEDATA, &trans);
    curl_easy_setopt_throw(curl, CURLOPT_WRITEFUNCTION, ResponseBodyHandler);

    curl_easy_setopt_throw(curl, CURLOPT_FILETIME, 1);
    curl_easy_setopt_throw(curl, CURLOPT_NOSIGNAL, 1);
    curl_easy_setopt_throw(curl, CURLOPT_TCP_NODELAY, 1); // disable Nagle
    curl_easy_setopt_throw(curl, CURLOPT_NETRC, CURL_NETRC_IGNORED);

#ifdef ALIYUN_TRACE
    curl_easy_setopt_throw(curl, CURLOPT_VERBOSE, 1);
#endif
    rc = curl_easy_perform(curl);
    if (rc != 0)
      throw CurlException(rc);

    return response;
  }

  int getStatus() const {
    return status_;
  }

  void setStatus(int status) {
    status_ = status;
  }

  bool isSuccess() const {
    if (200 <= status_ && 300 > status_)
      return true;
    return false;
  }

 private:
  struct HttpTransaction {
    CURL* curl_;
    HttpRequest* request_;
    HttpResponse* response_;
    size_t bodySends_;
    size_t bodyReceives_;

    enum {
      INIT,
      HEADER,
      BODY_IN,
      BODY_OUT,
      ABORT,
      DONE,
    } state_;

    HttpTransaction(CURL* curl, HttpRequest* req, HttpResponse* resp)
        : curl_(curl),
          request_(req),
          response_(resp),
          bodySends_(0),
          bodyReceives_(0),
          state_(INIT) {
    }
  };

  static size_t ResponseHeaderHandler(char *ptr, size_t size, size_t nmemb, void *userdata) {
    HttpTransaction* t = reinterpret_cast<HttpTransaction*>(userdata);
    size_t length = size * nmemb;

    t->state_ = HttpTransaction::HEADER;
    char* colon = ::strstr(ptr, ": ");
    if (colon != NULL) {
        std::string name(ptr, colon);
        char* crlf = ::strstr(ptr, "\r\n");
        std::string value(colon + 2, crlf ? crlf : ptr + length);
        t->response_->putHeaderParameter(name, value);
    }
    return length;
  }

  static size_t ResponseBodyHandler(char *ptr, size_t size, size_t nmemb, void *userdata) {
    HttpTransaction* t = reinterpret_cast<HttpTransaction*>(userdata);
    size_t length = size * nmemb;

#define curl_easy_getinfo_throw(curl, info, ptr) \
  rc = curl_easy_getinfo(curl, info, ptr);       \
  if (rc != CURLE_OK) throw CurlException(rc);

    // TODO: get response code, etc.
    CURLcode rc;
    long status;
    curl_easy_getinfo_throw(t->curl_, CURLINFO_RESPONSE_CODE, &status);
    t->response_->setStatus(status);

    // TODO: handle http bodys.
    t->state_ = HttpTransaction::BODY_IN;
    t->response_->content().append(ptr, ptr + length);
    t->bodyReceives_ += length;
    return length;
  }

  static size_t RequestBodyHandler(char *ptr, size_t size, size_t nmemb, void *userdata) {
    HttpTransaction* t = reinterpret_cast<HttpTransaction*>(userdata);
    size_t buffLen  = size * nmemb; // internal body buffer length.
    size_t contLen  = t->request_->getContent().length();
    long  bodyLeft = contLen - t->bodySends_;

    t->state_ = HttpTransaction::BODY_OUT;
    if (bodyLeft > 0) {
      size_t sendLen = bodyLeft < buffLen ? bodyLeft : buffLen;
      char* sendPtr = &(t->request_->getContent()[t->bodySends_]);
      ::memcpy(ptr, sendPtr, sendLen);
      t->bodySends_ += sendLen;
      return sendLen;
    }
    return 0;
  }

 private:
  int status_;
};

}  // namespace http

}  // namespace aliyun

#endif /* CORE_HTTP_HTTPRESPONSE_H_ */
